﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Data.SqlClient;
using System.Data.SqlTypes;

using Noris.Schedule.Support;
using Noris.Schedule.Support.Data;
using Noris.Schedule.Support.Sql;
using Noris.Schedule.Support.Core;
using Noris.Schedule.Support.DataFace;

using Noris.Schedule.Planning.DataFace;
using System.Collections;

namespace Noris.Schedule.Planning.ProcessData
{
	//    Komentář k T-Dokumentu:
	// Objekt třídy T-Dokument reprezentuje jeden konkrétní postup výroby jednoho dílce/výrobního příkazu pro danou technologii a množství.
	// Objekt neobsahuje konkrétní časy (termíny) výroby. Slouží jako obecná dokumentace pro plánování.
	// Objekt je univerzální nadstavbou nad konkrétními Noris třídami (VTPV / STPV / VP).
	// Tvorba objektu je řešena technikou ClassFactory: 
	//  objekt TDocumentCls má statický konstruktor CreateDocument(), 
	//  jemuž je (kromě specifikace konkrétního dílce) předána reference na objekt Factory, která naplní konkrétní data.
	// Objekt Factory je z hlediska Designu službou, která vrací potřebná data 
	//  (vyhledává operace, komponenty, zdroje a zajistí jejich konverzi na potřebný interface).
	// Skladba samotného objektu TDocumentCls je vždy stejná (hlavička, operace, na operacích zdroje a komponenty).
	// Data, která se do objektu umisťují, pocházejí z konkrétních Factory (jedna je pro STPV = třída TpvData, druhá pro VP = ProductData).
	// Vstupující data (operace, komponenty, zdroje) jsou třídy (např. Aps.ProcessData.TpvOperationCls), 
	//  které explicitně implementují potřebný interface (IOperationData), hostují v sobě základní datový objekt (AOperationCls) 
	//  a z tohoto datového objektu čtou a vracejí property povinné pro daný interface.
	//  Tyto "nadstavbové" třídy rozšiřují datovou základnu vstupních tříd 
	//  (např. ve třídě Aps.ProcessData.TpvOperationCls přidávají proměnné _QtyFinal a _QtyRequired, potřebné pro interface).
	// Do objektu TDocumentCls se umisťují přímo vrácené objekty.
	#region CLASS TDocumentCls : kompletní technologická dokumentace jednoho dílce podle jedné T modifikace anebo jednoho výrobního příkazu
	/// <summary>
	/// TDocumentCls : kompletní technologická dokumentace jednoho dílce podle jedné T modifikace / Výrobního příkazu.
	/// Obsahuje data o dílci, T modifikaci,  K modifikaci, soupis operací a soupis jejich komponent a zdrojů.
	/// </summary>
	public class TDocumentCls
	{
		#region KONSTRUKCE
		private TDocumentCls(TDocumentType documentType, int documentKey, decimal qtyFinal, TimeRange.TimeDirection timeDir)
		{
			_DocumentType = documentType;
			_DocumentKey = documentKey;
			_TimeDir = timeDir;
			_ConstrElementKey = SqlInt32.Null;
			_ConstrElement = null;
			_AOperationModKey = SqlInt32.Null;
			_AOperationMod = null;
			_AStructureModKey = SqlInt32.Null;
			_AStructureMod = null;
			_QtyFinal = qtyFinal;
			_OperationList = new List<IOperationData>();
		}
		/// <summary> { get; }
		/// Určuje typ dokumentace, která je v tomto objektu aktuálně přítomna (VTPV, STPV, VP)
		/// </summary>
		internal TDocumentType DocumentType { get { return _DocumentType; } }
		private TDocumentType _DocumentType;
		/// <summary> { get; }
		/// Určuje číslo klíče dokumentu, má význam ve spojení s typem dokumentu DocumentType (VTPV, STPV, VP).
		/// Pro VTPV jde o DÍLEC
		/// Pro STPV jde o T MODIFIKACI
		/// Pro VP jde o VÝROBNÍ PŘÍKAZ
		/// </summary>
		internal int DocumentKey { get { return _DocumentKey; } }
		private int _DocumentKey;
		/// <summary> { get; }
		/// Určuje směr, ve kterém jsou setříděny operace = v tomto směru bude probíhat alokace kapacit.
		/// </summary>
		internal TimeRange.TimeDirection TimeDir { get { return _TimeDir; } }
		private TimeRange.TimeDirection _TimeDir;
		/// <summary> { get; set; }
		/// Klíč dílce, Null=neznáme (není určeno) 0=není dílec
		/// </summary>
		internal SqlInt32 ConstrElementKey { get { return _ConstrElementKey; } set { _ConstrElementKey = value; _ConstrElement = null; } }
		private SqlInt32 _ConstrElementKey;
		/// <summary> { get; set; }
		/// Data o dílci
		/// </summary>
		internal ConstrElementCls ConstrElement { get { return _ConstrElement; } set { _ConstrElement = value; _ConstrElementKey = (value == null ? SqlInt32.Null : value.RecordNumber); } }
		private ConstrElementCls _ConstrElement;
		/// <summary> { get; set; }
		/// Klíč T modifikace, Null=neznáme (není určeno) 0=není dílec
		/// </summary>
		internal SqlInt32 AOperationModKey { get { return _AOperationModKey; } set { _AOperationModKey = value; _AOperationMod = null; } }
		private SqlInt32 _AOperationModKey;
		/// <summary> { get; set; }
		/// Data o T modifikaci
		/// </summary>
		internal AOperationModCls AOperationMod { get { return _AOperationMod; } set { _AOperationMod = value; _AOperationModKey = (value == null ? SqlInt32.Null : value.RecordNumber); } }
		private AOperationModCls _AOperationMod;
		/// <summary> { get; set; }
		/// Klíč K modifikace, Null=neznáme (není určeno) 0=není dílec
		/// </summary>
		internal SqlInt32 AStructureModKey { get { return _AStructureModKey; } set { _AStructureModKey = value; _AStructureMod = null; } }
		private SqlInt32 _AStructureModKey;
		/// <summary> { get; set; }
		/// Data o K modifikaci
		/// </summary>
		internal AStructureModCls AStructureMod { get { return _AStructureMod; } set { _AStructureMod = value; _AStructureModKey = (value == null ? SqlInt32.Null : value.RecordNumber); } }
		private AStructureModCls _AStructureMod;
		/// <summary> { get; }
		/// Soupis operací. Každý jednotlivý záznam obsahuje data o operaci STPV, o standardní operaci, a seznam dat o komponentách.
		/// </summary>
		internal List<IOperationData> OperationList { get { return _OperationList; } }
		private List<IOperationData> _OperationList;
		/// <summary> { get; }
		/// Příznak platnosti dat (je vyplněna T Mod, K mod, Dílec)
		/// </summary>
		internal bool IsValid
		{
			get
			{
				return
					this.AOperationMod != null &&
					this.AStructureMod != null &&
					this.ConstrElement != null;
			}
		}
		/// <summary> { get; set; }
		/// Množství finálního výrobku, na které je dokumentace přepočítaná, anebo zbývá vyrobit z výrobního příkazu.
		/// </summary>
		internal decimal QtyFinal { get { return _QtyFinal; } set { _QtyFinal = value; } }
		private decimal _QtyFinal;
		///<summary> { get; set; }
		///Množství chybějící, toto se bere do plánu
		///</summary>
		internal decimal QtyRequired { get { return _QtyRequired; } set { _QtyRequired = value; } }
		private decimal _QtyRequired;
		///<summary> { get; set; }
		///Příznak, že celá tato položka výroby je časově fixovaná (viz termíny FixedTime)
		///</summary>
		internal bool IsFixed { get { return _IsFixed; } set { _IsFixed = value; } }
		private bool _IsFixed;
		///<summary> { get; set; }
		///Časový interval, do něhož je celá tato položka výroby fixována. Akceptuje se pouze pokud je IsFixed == true.
		///</summary>
		internal TimeRange FixedTime { get { return _FixedTime; } set { _FixedTime = value; } }
		private TimeRange _FixedTime;
		/// <summary>
		/// Vizualizace
		/// </summary>
		/// <returns></returns>
		public override string ToString()
		{
			return "{Type=" + DocumentType + "; Key=" + DocumentKey + "; T mod=" + this.AOperationMod + "; Oper.Count=" + this.OperationList.Count + "}";
		}
		#endregion
		#region TVORBA OBJEKTU TDocumentCls a IOperationData
		/// <summary>
		/// Statický konstruktor. Na základě předaného klíče T modifikace nebo Výrobního příkazu 
		/// a knihovny STPV / VP vygeneruje a vrátí plný objekt konkrétní dokumentace.
		/// Pokud nenajde některý klíčový údaj, nechá je prázdné (null).
		/// </summary>
		/// <param name="tDocumentKey">Klíč T modifikace STPV nebo výrobního příkazu, jednoznačně určuje technologii</param>
		/// <param name="qtyFinal">Konkrétní množství finálu, pro které se má dokumentace rozkalkulovat. Záleží na implementaci knihovny (tLibrary).</param>
		/// <param name="sortOrder">Požadavek na pořadí třídění seznamu operací: ToFuture = ASCENDING, ToHistory = DESCENDING, Stay = netřídit</param>
		/// <param name="tLibrary">Reference na kompletní knihovnu technologické dokumentace (STPV/VP).</param>
		/// <returns></returns>
		internal static TDocumentCls CreateDocument(int tDocumentKey, decimal qtyFinal, TimeRange.TimeDirection sortOrder, ITechnologyLibrary tLibrary)
		{
			return CreateDocument(tDocumentKey, qtyFinal, sortOrder, tLibrary, null, null);
		}
		/// <summary>
		/// Statický konstruktor. Na základě předaného klíče T modifikace nebo Výrobního příkazu 
		/// a knihovny STPV / VP vygeneruje a vrátí plný objekt konkrétní dokumentace.
		/// Pokud nenajde některý klíčový údaj, nechá je prázdné (null).
		/// </summary>
		/// <param name="tDocumentKey">Klíč T modifikace STPV nebo výrobního příkazu, jednoznačně určuje technologii</param>
		/// <param name="qtyFinal">Konkrétní množství finálu, pro které se má dokumentace rozkalkulovat. Záleží na implementaci knihovny (tLibrary).</param>
		/// <param name="sortOrder">Požadavek na pořadí třídění seznamu operací: ToFuture = ASCENDING, ToHistory = DESCENDING, Stay = netřídit</param>
		/// <param name="tLibrary">Reference na kompletní knihovnu technologické dokumentace (STPV/VP).</param>
		/// <param name="fiFinder">Odkaz na metodu, která vyhledá klíč T dokumentu pro daný fiktivní dílec. Pokud nebude předáno, pak se fiktivní dílce nebudou rozpadat ale dostanou se na osu S.</param>
		/// <param name="userData">Objekt s uživatelskými daty, který se předává do metody fiFinder</param>
		/// <returns></returns>
		internal static TDocumentCls CreateDocument(int tDocumentKey, decimal qtyFinal, TimeRange.TimeDirection sortOrder, ITechnologyLibrary tLibrary, GetFictiousItemDocKeyDelegate fiFinder, object userData)
		{
			if (tLibrary == null) return null;

			// Základ dokumentu = jeho typ, klíč a finální množství:
			TDocumentCls tDocument = new TDocumentCls(tLibrary.CurrentType, tDocumentKey, qtyFinal, sortOrder);
			if (tDocumentKey == 0) return tDocument;

			// Hlavičková data = Základní tři objekty (T modifikace, K modifikace, Dílec):
			tLibrary.GetTDocumentData(tDocument, qtyFinal);

			// Seznam operací, kvůli fiktivním komponentám může jít o rekurzivní algoritmus:
			CreateDocumentAddOpArgs opArgs = new CreateDocumentAddOpArgs(tDocument, sortOrder, tLibrary, fiFinder, userData);
			_CreateDocumentAddOperations(opArgs, tDocument, tDocument.QtyFinal, false, (IOperationData)null);

			return tDocument;
		}
		/// <summary>
		/// Do předaného T dokumentu (objekt tDocument) přidá operace patřící uvedenému T dokumentu (parametr tDocumentKey).
		/// Toto řešení umožní rekurzivní volání této metody pro vkládání rozpadu fiktivních dílců.
		/// </summary>
		/// <param name="tDocument">Objekt T dokumentu, do něhož se přidávají operace včetně jejich komponent</param>
		/// <param name="tDocumentKey">Klíč T dokumentu, jehož operace a komponenty se nyní mají přidat</param>
		/// <param name="sortOrder">Směr setřídění operací</param>
		/// <param name="qtyFinal">Množství, na které má být přepočtena daná T dokumentace tDocumentKey</param>
		/// <param name="tDocHt">Soupis klíčů, které jsou přidány "nade mnou": 
		/// pokud na vstupu do této metody v tDocHt existuje klíč (tDocumentKey), pak jde o rekurzivní zacyklení.
		/// Jde o obranu proti zacyklení rekurze rozpadu vnořených fiktivních dílců.
		/// </param>
		/// <param name="tLibrary">Technologická knihovna</param>
		/// <param name="fiFinder">Odkaz na metodu, která vyhledá klíč T dokumentu pro daný fiktivní dílec. Pokud nebude předáno, pak se fiktivní dílce nebudou rozpadat ale dostanou se na osu S.</param>
		/// <param name="userData">Objekt s uživatelskými daty, který se předává do metody fiFinder</param>
		private static void _CreateDocumentAddOperations(CreateDocumentAddOpArgs opArgs, TDocumentCls tDocument, decimal qtyFinal, bool isFictious, IOperationData parentOperation)
//			TDocumentCls tDocument, int tDocumentKey, TimeRange.TimeDirection sortOrder, decimal qtyFinal, ref Hashtable tDocHt, ITechnologyLibrary tLibrary, GetFictiousItemDocKeyDelegate fiFinder, object userData)
		{
			// Došlo zde k zacyklení?
			if (tDocHt.ContainsKey(tDocument.DocumentKey))
			{
				RecordCls tDocRecordMain = tLibrary.GetTDocumentRecord(tDocument.DocumentKey, NotFindMode.Null); // Záznam hlavičky TDoc hlavního dílce
				RecordCls tDocRecordCurr = tLibrary.GetTDocumentRecord(tDocument.DocumentKey, NotFindMode.Null);          // Záznam hlavičky TDoc zdejšího dílce
				PlanningControlCls.Current.AddInfo(PlanningResultSeverityType.UserError, PlanningResultCircumstanceType.TechnologyFictiousItemCycle, (IRecordKey)tDocRecordCurr,
					MessageInfo.Get("Chyba v technologii dílce %0: fiktivní dílec %1 je zacyklený (používá sám sebe).", 
					(tDocRecordMain == null ? "null" : tDocRecordMain.ReferNazev), 
					(tDocRecordCurr == null ? "null" : tDocRecordCurr.ReferNazev) ));
				return;
			}
			tDocHt.Add(tDocument.DocumentKey, null);         // To aby nedošlo k zacyklení.

			// Budou se provádět rozpady fiktivních dílců?
			bool decompositeFictiousItem = (tLibrary.AllowFictiousItem && fiFinder != null);

			List<IOperationData> aopList = tLibrary.GetOperationDataList(tDocument.DocumentKey, sortOrder, qtyFinal, NotFindMode.GetFromDbNull);
			if (aopList != null)
			{
				foreach (IOperationData operation in aopList)
				{
					// Komponenty operace (některá z nich může být fiktivní, takové si vyřešíme jinak, a nebudou se vkládat do její operace:
					List<IStructureData> structList = tLibrary.GetStructureDataList(operation.Operation, qtyFinal);
					List<IStructureData> fictItList = new List<IStructureData>(); // Souhrn fiktivních komponent: vytahám je ze seznamu komponent.
					foreach (IStructureData structItem in structList)
					{
						if (structItem.FictiousItem && decompositeFictiousItem)
							// Fiktivní komponenta, a T library ji dokáže rozpadnout?
							fictItList.Add(structItem);
						else
							operation.StructureList.Add(structItem);
					}

					// Zdroje operací jsou jednoduché, načtou se z T-Library a vloží do operace:
					operation.ResourceList.AddRange(tLibrary.GetResourceDataList(operation.Operation, tDocument.QtyFinal));

					// Nyní máme sestavenou operaci (operation), která patří do tDocument._OperationList.
					if (fictItList.Count == 0)
					{	// Pokud neexistují fiktivní komponenty, přidám operaci bez nějakých cavyků:
						tDocument._OperationList.Add(operation);
					}
					else
					{	// Pokud ale existují fiktivní komponenty, je třeba je konkretizovat a to včetně jejich operací.
						// Pořadí vkládání operací fiktivních komponent a vložení zdejší operace závisí na směru času (sortOrder)
						if (sortOrder == TimeRange.TimeDirection.ToHistory)
						{	// Při směru ToHistory vložím nejprve naší operaci (např. 40), 
							//  a teprve za ni budu vkládat rozložený fiktivní dílec (jichž může být více).
							tDocument._OperationList.Add(operation);
						}
						
						// Vložit rozpady fiktivních dílců:
						foreach (IStructureData fictItItem in fictItList)
						{	// Pro každý fiktivní dílec v aktuální operaci:
							int fictItemKey = fiFinder(fictItItem, userData);   // Vyhledám klíč T dokumentu tohoto dílce
							if (fictItemKey != 0)       // a rekurzivně zavolám rozepsání operací tohoto fiktivního dílce do výstupní T dokumentace:
							{
								_CreateDocumentAddOperations(tDocument, fictItemKey, sortOrder, fictItItem.QtyRequired, ref tDocHt, tLibrary, fiFinder, userData);
							}
						}

						if (sortOrder != TimeRange.TimeDirection.ToHistory)
						{	// Při směru jiném než ToHistory vložím naší operaci (např. 10) až na konec, poté kdy jsou vloženy operace jejích fiktivních dílců:
							// Jinak řečeno: při tvorbě dokumentace v přirozeném směru času (do budoucnosti)
							//   nejprve vložím operace podřízených dílců, ty se podle nich vyrobí, 
							//   a až budou vyrobeny, tak teprve potom mohou vstoupit jako komponenty do "své" operace:
							tDocument._OperationList.Add(operation);
						}
					}
				}
			}

			tDocHt.Remove(tDocument.DocumentKey);             // S touto položkou jsme skončili, zacyklení nenastane, ale příště ji lze opět vložit.
		}
		/// <summary>
		/// Vytvoří a vrátí data o požadované operaci
		/// </summary>
		/// <param name="tDocumentKey">Klíč dokumentu (Dílec VTPV, T-modifikace STPV, Výrobní příkaz)</param>
		/// <param name="operation">Klíč operace v rámci dílce</param>
		/// <param name="qtyFinal">Konkrétní množství finálu, pro které se má dokumentace rozkalkulovat. Záleží na implementaci knihovny (tLibrary).</param>
		/// <param name="tLibrary">Reference na kompletní knihovnu technologické dokumentace (STPV/VP).</param>
		/// <returns></returns>
		internal static IOperationData CreateOperation(int tDocumentKey, int operation, decimal qtyFinal, ITechnologyLibrary tLibrary, NotFindMode notFindMode)
		{
			IOperationData operationData = tLibrary.GetOperationData(tDocumentKey, operation, qtyFinal, notFindMode);
			if (operationData != null)
			{
				operationData.StructureList.AddRange(tLibrary.GetStructureDataList(operationData.Operation, qtyFinal));
				operationData.ResourceList.AddRange(tLibrary.GetResourceDataList(operationData.Operation, qtyFinal));
			}
			return operationData;
		}
		/// <summary>
		/// Vytvoří a vrátí testovací T dokument. Je prázdný, obsahuje jen předané údaje, ale žádné operace.
		/// </summary>
		/// <param name="docType">Typ dokumentu</param>
		/// <param name="tDocumentKey">Klíč dokumentu</param>
		/// <param name="qtyFinal">Finální množství</param>
		/// <param name="sortOrder">Směr dokumentu</param>
		/// <returns></returns>
		internal static TDocumentCls CreateDocumentTest(TDocumentType docType, int tDocumentKey, decimal qtyFinal, TimeRange.TimeDirection sortOrder)
		{
			TDocumentCls tDocument = new TDocumentCls(docType, tDocumentKey, qtyFinal, sortOrder);
			return tDocument;
		}
		#endregion
	}
	#endregion
	#region CLASS CreateDocumentAddOpArgs : argumenty (data) pro metodu TDocumentCls._CreateDocumentAddOperations()
	/// <summary>
	/// CreateDocumentAddOpArgs : argumenty (data) pro metodu TDocumentCls._CreateDocumentAddOperations().
	/// Dat je mnoho, často putují ty samé objekty opakovaně stále dokola (metoda je rekurzivní).
	/// </summary>
	internal class CreateDocumentAddOpArgs
	{
		internal CreateDocumentAddOpArgs(TDocumentCls tDocument, TimeRange.TimeDirection sortOrder, ITechnologyLibrary tLibrary, GetFictiousItemDocKeyDelegate fiFinder, object userData)
		{
			this.TDocument = tDocument;
			this.SortOrder = sortOrder;
			this.TDocHt = new Hashtable();
			this.TLibrary = tLibrary;
			this.FiFinder = fiFinder;
			this.UserData = userData;
		}
		internal TDocumentCls TDocument { get; private set;}
		internal int TDocumentKey { get { return TDocument.DocumentKey; } }
		internal TimeRange.TimeDirection SortOrder { get; private set; }
		internal Hashtable TDocHt { get; private set; }
		internal ITechnologyLibrary TLibrary { get; private set; }
		internal GetFictiousItemDocKeyDelegate FiFinder { get; private set; }
		internal object UserData { get; private set; }
	}
	#endregion
}
